﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Text;
using System.Windows.Browser;
using System.Xml;
using DiscoveryLogic.UI.Layouts.Controls;
using System.Windows.Markup;


namespace DiscoveryLogic.UI.Layouts.Controls
{

    public class PointSeries
    {
        public double MinX;
        public double MaxX;
        public double MinY;
        public double MaxY;
        public string Name;

        public List<Point> Points = new List<Point>();
    };



    public class PointSeriesLineData
    {
        public PointSeriesLineData(PointSeries series)
        {
            this.Series = series;
        }
        public PointSeries Series;
        public Path Path;

    }
    public enum LineCursor
    {
        None,
        HorizontalLine,
        VerticalLine,
        TextTip,
        Dot
    };


    public class LineChart : XYChart
    {
#if INHERIT 
        private const string DataCanvasName = "Data";
        private const string TitleName = "Title";
        private const string XAxisLabelsName = "XAxisLabels";
        private const string YAxisLabelsName = "YAxisLabels";
        private const string LegendCanvasName = "LegendsCanvas";
        private const string LegendPathPrefix = "legendPath";
        private const string LegendTextPrefix = "legendText";
        private const string LabelXName = "LabelX";
        private const string LabelYName = "LabelY";

        private const string DataCanvasVerticalCursorName = "DataVerticalCursor";
        private const string DataCanvasHorizontalCursorName = "DataHorizontalCursor";
        private const string ToolTipName = "Tooltip";
        private const string ToolTipTextName = "TooltipText";


        private Canvas CanvasRoot;
        private Canvas _dataCanvas;
        private Canvas _xAxisCanvas;
        private Canvas _yAxisCanvas;
        private Canvas _legendCanvas;
        private TextBlock _title;
        private TextBlock _labelX;
        private TextBlock _labelY;
        private Canvas _toolTip;
        private TextBlock _toolTipText; 
#endif

        private bool showScale = true;

        public bool ShowScale
        {
            get
            {
                return showScale;
            }
            set
            {
                showScale = value;
            }
        }

        private bool showLegends = true;

        public bool ShowLegends
        {
            get
            {
                return showLegends;
            }
            set
            {
                showLegends = value;
            }

        }

        public bool ShowBorder
        {
            set
            {
                Rectangle r = CanvasRoot.FindName("DataBorder") as Rectangle;
                r.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        public bool ShowDataBottomBorder
        {
            set
            {
                Path p = CanvasRoot.FindName("Data_BottomBorder") as Path;
                p.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        public bool ShowDataLeftBorder
        {
            set
            {
                Path p = CanvasRoot.FindName("Data_LeftBorder") as Path;
                p.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
            }
        }


        public LineChart()
        {
            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("DiscoveryLogic.UI.Layouts.Controls.LineChart.xaml");
            this.CanvasRoot = XamlReader.Load(new System.IO.StreamReader(s).ReadToEnd()) as Canvas;
            CanvasRoot = (Canvas)CanvasRoot.FindName("LineChartMain");
            InitControls();
            HtmlPage.RegisterScriptableObject(GetInstanceName(), this);
            this.Loaded += new RoutedEventHandler(LineChart_Loaded);

            //System.IO.Stream stream = this.GetType().Assembly.GetManifestResourceStream("DiscoveryLogic.UI.Layouts.Controls.ResourceDictionary1.xaml");
            //XmlReader rdr = XmlReader.Create(stream);

            //    ResourceHelper.LoadXml ( rdr , false ); 

        }

        void LineChart_Loaded(object sender, EventArgs e)
        {
            this._dataCanvas.MouseMove += new MouseEventHandler(_dataCanvas_MouseMove);
            //InternalAddSeries("10,10, 20,20, 30,30, 40,40, 50,50, 55,55", "Series1");
            //InternalAddSeries("5,5, 10,20, 20,0, 30,10, 40,20, 65,55, 200, 120, 330, 200", "Series2");
            //InternalAddSeries("15,5, 30,20, 50,0, 80,10, 120,20, 165,155", "Series3");
            ////InternalAddSeries("15,35, 30,50, 50,50, 80,90, 120,20, 165,255", "Series3");

            //InternalAddSeries("115,105, 130,120, 150,0, 180,10, 220,20, 265,255", "Series3");
            ////InternalAddSeries("15,35, 30,50, 50,50, 80,90, 120,20, 165,255", "Series3");

            //InternalAddSeries("115,105, 130,120, 150,0, 180,10, 220,20, 265,255", "Series3");

            ScaleToSize();

            Draw();
        }






        void _dataCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            Point pos = e.GetPosition(this._dataCanvas);

            switch (this._mouseCursor)
            {
                case LineCursor.HorizontalLine:
                    {
                        _cursor.SetValue(Canvas.TopProperty, pos.Y);
                    }
                    break;
                case LineCursor.VerticalLine:
                    {
                        _cursor.SetValue(Canvas.LeftProperty, pos.X);
                        foreach (PointSeriesLineData psld in InternalSeries)
                        {
                            Point p = e.GetPosition(psld.Path);
                            System.Diagnostics.Debug.WriteLine("Hit at " + psld.Series.Name + ": " + p.ToString());
                        }
                    }
                    break;

            }

        }

        protected new Color[] colors = new Color[]
                          { 
                            Color.FromArgb(255, 0x2E, 0x5B, 0x95) ,  //Blue
                            Color.FromArgb(255, 0xC5, 0x5E, 0x93) ,  //Pink
                            Color.FromArgb(255, 0x7F, 0x4B, 0x92) ,  //Purple
                            Color.FromArgb(255, 0x44, 0x89, 0x44) ,  //Green
                            Color.FromArgb(255, 0xC5, 0xA9, 0x5E)  
                            };


        [ScriptableMember]
        public void AddSeries(string s, string seriesName)
        {
            InternalAddSeries(s, seriesName);
            ReDraw();
        }

        [ScriptableMember]
        public void Clear(string series)
        {
            if (series != null && series != string.Empty)
            {
                foreach (PointSeriesLineData p in InternalSeries)
                {
                    if (p.Series.Name == series)
                    {
                        InternalSeries.Remove(p);
                        break;
                    }
                }
            }
            else
                InternalSeries.Clear();


            ReDraw();
        }

        protected PointSeries InternalAddSeries(string seriesName, string s)
        {
            string[] r = s.Split(',');

            PointSeries pointseries = new PointSeries();
            double xMin = double.PositiveInfinity;
            double yMin = Double.PositiveInfinity;
            double xMax = double.NegativeInfinity;
            double yMax = double.NegativeInfinity;

            for (int x = 0; x < r.Length - 1; x += 2)
            {
                double dx = System.Convert.ToDouble(r[x]);
                double dy = System.Convert.ToDouble(r[x + 1]);
                pointseries.Points.Add(new Point(dx, dy));
                xMin = Math.Min(dx, xMin);
                xMax = Math.Max(dx, xMax);
                yMin = Math.Min(dy, yMin);
                yMax = Math.Max(dy, yMax);
            }
            pointseries.MaxX = xMax;
            pointseries.MaxY = yMax;
            pointseries.MinX = xMin;
            pointseries.MinY = yMin;
            pointseries.Name = seriesName;

            InternalSeries.Add(new PointSeriesLineData(pointseries));

            return pointseries;

        }


        //TODO: does this need to be exposed back 
        //public List<PointSeries> Series
        //{
        //    get {
        //        List<PointSeries> list = new List<PointSeries>();

        //        foreach (PointSeriesLineData linedata in InternalSeries)
        //        {
        //            list.Add(linedata.series);
        //        }

        //        return list; 
        //    }

        //}

        protected List<PointSeriesLineData> InternalSeries
        {
            get
            {
                return _series;
            }
        }
        private List<PointSeriesLineData> _series = new List<PointSeriesLineData>();



        protected void GetScale(List<PointSeriesLineData> series, double plotSpaceWidth, double height,
            double xLabelWidth, double yLabelHeight,
            out double scaleX, out double scaleY, out double startX, out double endX, out double startY, out double endY)
        {
            double xMin = double.PositiveInfinity, yMin = double.PositiveInfinity;
            double xMax = double.NegativeInfinity, yMax = double.NegativeInfinity;
            foreach (PointSeriesLineData pw in series)
            {
                PointSeries p = pw.Series;

                xMin = Math.Min(xMin, p.MinX);
                xMax = Math.Max(xMax, p.MaxX);

                yMin = Math.Min(yMin, p.MinY);
                yMax = Math.Max(yMax, p.MaxY);

            }


            double xRange = xMax - xMin;
            double yRange = yMax - yMin;
            if (xRange > 10)
            {
                double d = Math.Pow(10, Math.Max(Math.Floor(Math.Log10(xMin)), 1));
                if (d != 0)
                    xMin -= (xMin % d);

                d = Math.Pow(10, (Math.Floor(Math.Log10(xMax))));
                if (d != 0 && xMax % d != 0)
                {
                    xMax += (d - (xMax % d));
                }

            }

            if (yRange > 10)
            {
                double d = Math.Pow(10, Math.Max(Math.Floor(Math.Log10(yMin)), 1));
                if (d != 0)
                    yMin -= (yMin % d);

                d = Math.Pow(10, (Math.Floor(Math.Log10(yMax))));
                if (d != 0 && yMax % d != 0)
                {
                    yMax += (d - (yMax % d));
                }
            }


            scaleX = (plotSpaceWidth) / (xMax - xMin);
            scaleY = (height) / (yMax - yMin);


            startX = xMin;
            endX = xMax;
            startY = yMin;
            endY = yMax;

        }

        //protected void GetScale(List<PointSeries> series, double plotSpaceWidth, double height,
        //    double xLabelWidth, double yLabelHeight,
        //    out double scaleX, out double scaleY, out double startX, out double endX, out double startY, out double endY)
        //{
        //    double xMin = double.PositiveInfinity, yMin = double.PositiveInfinity;
        //    double xMax = double.NegativeInfinity, yMax = double.NegativeInfinity;
        //    foreach (PointSeries p in series)
        //    {
        //        xMin = Math.Min(xMin, p.MinX);
        //        xMax = Math.Max(xMax, p.MaxX);

        //        yMin = Math.Min(yMin, p.MinY);
        //        yMax = Math.Max(yMax, p.MaxY);

        //    }


        //    double xRange = xMax - xMin;
        //    double yRange = yMax - yMin;
        //    if (xRange > 10)
        //    {
        //        double d = Math.Pow(10, (Math.Floor(Math.Log10(xRange))));
        //        if (d > xRange * .6)
        //        {
        //            d = Math.Max(d * 0.9, xRange * 0.6);
        //            d = Math.Pow(10, (Math.Floor(Math.Log10(d))));
        //        }

        //        xMin -= (xMin % d);
        //        xMax += (d - (xMax % d));
        //    }

        //    if (yRange > 10)
        //    {
        //        double d = Math.Pow(10, (Math.Floor(Math.Log10(yRange))));
        //        if (d > yRange * .6)
        //        {
        //            d = Math.Max(d * 0.9, yRange * 0.6);
        //            d = Math.Pow(10, (Math.Floor(Math.Log10(d))));
        //        }

        //        yMin -= (yMin % d);
        //        yMax += (d - (yMax % d));
        //    }


        //    scaleX = (plotSpaceWidth) / (xMax - xMin);
        //    scaleY = (height) / (yMax - yMin);


        //    startX = xMin;
        //    endX = xMax;
        //    startY = yMin;
        //    endY = yMax;

        //} 



        protected override void ReDraw()
        {
            this._userData.Children.Clear();
            this._gridLines.Children.Clear();
            this._xAxisCanvas.Children.Clear();
            this._yAxisCanvas.Children.Clear();

            this._legendCanvas.Children.Clear();
            Draw();
        }

        protected override void Draw()
        {
            double scaleX;
            double scaleY;
            double startPointX = 0;
            double startPointY = 0;
            double endPointX = 0;
            double endPointY = 0;

            double width = this.CanvasRoot.Width;
            double height = this.CanvasRoot.Height;

            double xLabelWidth = 20;
            double yLabelHeight = 20;

            if (this.InternalSeries.Count == 0)
                return;


            GetScale(InternalSeries, _dataCanvas.Width, _dataCanvas.Height,
                xLabelWidth, yLabelHeight,
                out scaleX, out scaleY, out startPointX, out endPointX,
                out startPointY, out endPointY);

            this._scaleX = scaleX;
            this._scaleY = scaleY;
            this._startPointX = startPointX;
            this._startPointY = startPointY;
            this._endPointY = endPointY;

            if (showLegends)
            {
                DrawLegends();
            }

            if (showScale)
            {
                DrawScale(startPointX, endPointX, startPointY, endPointY, scaleX, scaleY);
            }

            for (int x = 0; x < InternalSeries.Count; x++)
            {
                Path path = DrawSeries(InternalSeries[x].Series, scaleX, scaleY, startPointX, endPointY, GetBrush(x));

                InternalSeries[x].Path = path;
                path.MouseEnter += new MouseEventHandler(path_MouseEnter);
                path.MouseLeave += new MouseEventHandler(path_MouseLeave);

                this._userData.Children.Add(path);
                //  this._dataCanvas.Children.Add(path); 
            }
            DrawLabels();

        }



        protected void DrawLegends()
        {
            this._legendCanvas.Children.Clear();
            string template =
            "<Canvas xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'  x:Name='legend___' Width='115' Height='25'>"
                + "<Path Fill='#FFFFFFFF' Stretch='Fill' Stroke='#FF000000' x:Name='legendPath___' Width='30' Height='1' Canvas.Left='4' Canvas.Top='14.5' Data='M4.5,15 L25.868421,15'/>"
                + "<TextBlock x:Name='legendText___' Width='87' Height='15' Canvas.Left='39' Canvas.Top='7' FontSize='12' TextWrapping='Wrap' FontWeight='Bold' >TextBlock</TextBlock>"
            + "</Canvas>";

            double leftoffset = 0;
            double topoffset = 0;

            for (int x = 0; x < InternalSeries.Count; x++)
            {
                string canvas = template.Replace("___", x.ToString());
                Canvas c = XamlReader.Load(canvas) as Canvas;

                Path p = c.FindName(LegendPathPrefix + x.ToString()) as Path;
                if (p != null) p.Stroke = GetBrush(x);
                TextBlock t = c.FindName(LegendTextPrefix + x.ToString()) as TextBlock;
                if (t != null) t.Text = InternalSeries[x].Series.Name;

                if (leftoffset + c.Width >= this._legendCanvas.Width)
                {
                    leftoffset = 0;
                    topoffset += c.Height;
                }


                c.SetValue(Canvas.LeftProperty, (double)leftoffset);
                leftoffset += c.Width;

                c.SetValue(Canvas.TopProperty, (double)topoffset);
                this._legendCanvas.Children.Add(c);
            }
        }

        protected void DrawScale(double minX, double maxX, double minY, double maxY, double scaleX,
            double scaleY)
        {
            TextBlock measureText = new TextBlock();
            measureText.FontSize = 10;
            measureText.Text = maxX.ToString();
            double labelWidth = measureText.ActualWidth;
            double labelHeight = measureText.ActualHeight;

            double scaledwidth = this._xAxisCanvas.Width / scaleX;

            int xLabels = System.Convert.ToInt32(this._xAxisCanvas.Width / (labelWidth * 1.4));

            if (xLabels > 10)
            {
                xLabels = 10;
            }
            int step = System.Convert.ToInt32(this._xAxisCanvas.Width / xLabels);
            int scaledstep = System.Convert.ToInt32(step / scaleX);

            double centerX = labelWidth / 2;
            for (int x = 1; x < xLabels; x++)
            {
                TextBlock t = new TextBlock();
                t.FontSize = 10;
                t.Text = (minX + (scaledstep * x)).ToString();
                t.SetValue(Canvas.LeftProperty, (double)(step * x) - (t.ActualWidth / 2));
                this._xAxisCanvas.Children.Add(t);
                if (ShowVerticalGridLines)
                {
                    Line l = new Line();
                    l.X1 = (step * x); l.Y1 = 0;
                    l.X2 = (step * x); l.Y1 = this._dataCanvas.Height;
                    l.Stroke = new SolidColorBrush(Colors.Gray);
                    l.StrokeThickness = 1;
                    this._gridLines.Children.Add(l);
                }
            }


            measureText.Text = maxY.ToString();
            labelWidth = measureText.ActualWidth;
            labelHeight = measureText.ActualHeight;

            double scaledheight = this._xAxisCanvas.Height / scaleY;

            int yLabels = System.Convert.ToInt32(this._yAxisCanvas.Height / (labelHeight * 1.6));
            if (yLabels > 10)
            {
                yLabels = 10;
            }
            step = System.Convert.ToInt32(this._yAxisCanvas.Height / yLabels);
            scaledstep = System.Convert.ToInt32(step / scaleY);


            for (int x = 1; x < yLabels; x++)
            {
                TextBlock t = new TextBlock();
                t.FontSize = 10;
                t.Text = (maxY - (scaledstep * x)).ToString();
                t.SetValue(Canvas.TopProperty, (double)(step * x) - (t.ActualHeight / 2));
                this._yAxisCanvas.Children.Add(t);

                if (ShowHorizontalGridLines)
                {
                    Line l = new Line();
                    l.X1 = 0; l.Y1 = (step * x);
                    l.X2 = this._dataCanvas.Width; ; l.Y2 = (step * x);
                    l.Stroke = new SolidColorBrush(Colors.Gray);
                    l.StrokeThickness = 1;

                    this._gridLines.Children.Add(l);
                }
            }

        }





        protected Path DrawSeries(PointSeries p, double scaleX, double scaleY, double startPointX, double startPointY, Brush strokeBrush)
        {
            StringBuilder sb = new StringBuilder();


            double startX = ((p.Points[0].X - startPointX) * scaleX);
            double startY = ((startPointY - p.Points[0].Y) * scaleY);


            sb.Append("<Path  StrokeThickness='2' Data='M " + startX.ToString() + "," + startY.ToString() + " L ");
            for (int a = 1; a < p.Points.Count; a++)
            {
                sb.AppendFormat(" {0},{1} ",
                        ((p.Points[a].X - startPointX) * scaleX),
                        ((startPointY - p.Points[a].Y) * scaleY)
                    );
            }
            sb.Append("' />");

            Path path = XamlReader.Load(sb.ToString()) as Path;
            path.Stroke = strokeBrush;
            return path;

        }

        void path_MouseLeave(object sender, EventArgs e)
        {
            this._toolTip.Visibility = Visibility.Collapsed;
        }

        void path_MouseEnter(object sender, MouseEventArgs e)
        {
            Point datacoord = e.GetPosition(this._dataCanvas);
            // Don't show tooltip
            //this._toolTip.Visibility = Visibility.Visible;
            this._toolTip.SetValue(Canvas.LeftProperty, (double)datacoord.X + 3);
            this._toolTip.SetValue(Canvas.TopProperty, (double)datacoord.Y + 3);

            this._toolTipText.Text = string.Format
            ("{0:#},{1:#}", ToXValue(datacoord.X), ToYValue(datacoord.Y));

        }

        public PointSeriesLineData[] GetLines()
        {
            return this._series.ToArray();
        }



#if INHERIT 
        public bool ShowYLabel
        {
            get
            {
                return this._labelY.Visibility == Visibility.Visible ; 
                
            }
            set
            {
                if (value)
                {
                    this._labelY.Visibility = Visibility.Visible;
                }
                else
                {
                    this._labelY.Visibility = Visibility.Collapsed;
                } 
            }
        }



        public bool ShowXLabel
        {
            get
            {
                return this._labelX.Visibility == Visibility.Visible;

            }
            set
            {
                if (value)
                    this._labelX.Visibility = Visibility.Visible;
                else
                    this._labelX.Visibility = Visibility.Collapsed;
            }
        }


        protected LineCursor _mouseCursor; 
        public string MouseCursor
        {
            get
            {
                return this._mouseCursor.ToString(); 

            }
            set
            {
                try
                {
                    LineCursor c = (LineCursor) Enum.Parse(typeof(LineCursor), value, true);
                    if (c != _mouseCursor)
                    {

                        ChangeCursor ( _mouseCursor, false ); 
                        ChangeCursor ( c , true ) ; 
                        _mouseCursor = c; 
                    }                    
                }
                finally { } 
            } 
        } 

             protected double _scaleX;
        protected double _scaleY;
        protected double _startPointX;
        protected double _startPointY;
        protected double _endPointY;

        void ChangeCursor ( LineCursor cursor, bool show ) 
        { 
            string elementName = string.Empty ;
            switch (cursor)
            {
                case LineCursor.VerticalLine:
                    elementName = DataCanvasVerticalCursorName;
                    break;
                case LineCursor.HorizontalLine:
                    elementName = DataCanvasHorizontalCursorName;
                    break;

            }

            UIElement e = this._dataCanvas.FindName(elementName) as UIElement;
            if (e != null)
            {
                if (!show)
                    e.Visibility = Visibility.Collapsed;
                else
                    e.Visibility = Visibility.Visible;

                _cursor = e as Path; 
            } 
        }

        Path _cursor = null; 

        void ChangeCursor ( LineCursor cursor, bool show ) 
        { 
            string elementName = string.Empty ;
            switch (cursor)
            {
                case LineCursor.VerticalLine:
                    elementName = DataCanvasVerticalCursorName;
                    break;
                case LineCursor.HorizontalLine:
                    elementName = DataCanvasHorizontalCursorName;
                    break;

            }

            UIElement e = this._dataCanvas.FindName(elementName) as UIElement;
            if (e != null)
            {
                if (!show)
                    e.Visibility = Visibility.Collapsed;
                else
                    e.Visibility = Visibility.Visible;

                _cursor = e as Path; 
            } 
        }

        Path _cursor = null; 
#endif
    }
}
